﻿// Copyright (c) 2012, Alexander Endris
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System.Collections.Generic;
using System.IO;
using System.Text;
using Lyricsgrabber.Core.ID3.Frames;

namespace Lyricsgrabber.Core.ID3
{
  public static class ID3FrameFactory
  {
    public static ID3Frame Create(BinaryReader reader, string frameId, int size, short flags)
    {
      switch (frameId)
      {
        case "TALB":
          {
            FrameTextEncoding encoding = (FrameTextEncoding) reader.ReadByte();
            return new ID3FrameAlbum(GetEncodedString(reader, size - 1, encoding));
          }
        case "TIT2":
          {
            FrameTextEncoding encoding = (FrameTextEncoding) reader.ReadByte();
            return new ID3FrameTitle(GetEncodedString(reader, size - 1, encoding));
          }
        case "TPE1":
          {
            FrameTextEncoding encoding = (FrameTextEncoding) reader.ReadByte();
            return new ID3FrameArtist(GetEncodedString(reader, size - 1, encoding));
          }
        case "TRCK":
          {
            FrameTextEncoding encoding = (FrameTextEncoding) reader.ReadByte();
            return new ID3FrameTrack(GetEncodedString(reader, size-1, encoding));
          }
        case "USLT":
          {
            FrameTextEncoding encoding = (FrameTextEncoding) reader.ReadByte();
            int bytesRead;
            string lang = GetEncodedString(reader, 3, FrameTextEncoding.ISO88591);
            string descriptor = GetEncodedTerminatedString(reader, encoding, out bytesRead);
            string lyrics = GetEncodedString(reader, size - 4 - bytesRead, encoding);

            return new ID3FrameUnsyncedLyrics(lang, descriptor, lyrics);
          }
        case "TXXX":
          {
            FrameTextEncoding encoding = (FrameTextEncoding) reader.ReadByte();
            int bytesRead;
            string description = GetEncodedTerminatedString(reader, encoding, out bytesRead);
            string text = GetEncodedTerminatedString(reader, encoding,  out bytesRead);

            return new ID3FrameUserDefinedText(description, text);
          }
        default:
          reader.BaseStream.Seek(size, SeekOrigin.Current);
          return null;
      }
    }

    public static string GetEncodedTerminatedString(BinaryReader reader, FrameTextEncoding encoding, out int bytesRead)
    {
      List<byte> data = new List<byte>();
      bytesRead = 0;
      while (true)
      {
        byte temp1 = reader.ReadByte();
        bytesRead++;
        byte temp2 = 0;

        // get two bytes for unicode
        if (encoding == FrameTextEncoding.Unicode)
        {
          temp2 = reader.ReadByte();
          bytesRead++;
        }

        // if zero then the String is finished
        if (temp1 == 0 && temp2 == 0)
        {
          break;
        }

        data.Add(temp1);

        // Add second byte for unicode
        if (encoding == FrameTextEncoding.Unicode)
        {
          data.Add(temp2);
        }
      }

      switch (encoding)
      {
        case FrameTextEncoding.ISO88591:
          return Encoding.GetEncoding("ISO-8859-1").GetString(data.ToArray()).TrimEnd('\0');
        case FrameTextEncoding.Unicode:
          byte first = data[0];
          byte second = data[1];
          if (first > second)
            return Encoding.Unicode.GetString(data.GetRange(2, data.Count - 2).ToArray()).TrimEnd('\0');
          return Encoding.BigEndianUnicode.GetString(data.GetRange(2, data.Count - 2).ToArray()).TrimEnd('\0');
        default:
          return "";
      }
    }

    private static string GetEncodedString(BinaryReader reader, int size, FrameTextEncoding encoding)
    {
      switch (encoding)
      {
        case FrameTextEncoding.ISO88591:
          return Encoding.GetEncoding("ISO-8859-1").GetString(reader.ReadBytes(size)).TrimEnd('\0');
        case FrameTextEncoding.Unicode:
          byte first = reader.ReadByte();
          byte second = reader.ReadByte();
          if (first > second)
            return Encoding.Unicode.GetString(reader.ReadBytes(size - 2)).TrimEnd('\0');
          return Encoding.BigEndianUnicode.GetString(reader.ReadBytes(size - 2)).TrimEnd('\0');
        default:
          return "";
      }
    }
  }
}